<?php

namespace Aoe\Core;

use Aoe\Attributes\ClassInterface;
use Aoe\Attributes\ParameterInterface;
use ReflectionClass;
use Throwable;
use Aoe\Util\Exception;


/**
 * # 容器
 *  * has(), get() : 缓存优先,
 *  * createObject() : 新建
 *
 *   ！！！在 Container 容器（包括Kernel）中，通过虚拟方法加载类，必须提前在配置文件中注册
 *
 * ======================= 单例 ============================
 * @method Kernel Kernel() 内核
 */
class Container
{
    /**
     * 配置文件中的KEY
     */
    const string KEY_SERVICE           = 'service';
    /**
     * 错误信息: 未找到相应的类定义
     */
    const string ERR_CLS_NOT_FOUND     = '找不到${class}类定义';
    /**
     * 错误信息: 创建实例失败
     */
    const string ERR_OBJ_CREATE_FAILED = '对象${obj}创建失败${err}';
    
    /**
     * @var array 生成的对象
     */
    private array $objects = [];
    
    /**
     * @param array<array{string | array{full: string, singel: bool}> $definitions
     */
    public function __construct(private readonly array $definitions)
    {
        // 保存自己
        $this->objects[basename(__CLASS__)] = $this;
    }
    
    /**
     * ## 获取服务
     *
     * @param string $name Identifier of the entry to look for.
     *
     * @return object Entry.
     * @throws \Exception
     */
    public function get(string $name): object
    {
        if (!isset($this->objects[$name])) $this->_auto_set($name);
        return $this->objects[$name];
    }
    
    /**
     * 根据类简称找到配置并按需创建对象
     *
     * @param string $name
     *
     * @throws \Exception
     */
    private function _auto_set(string $name): void
    {
        $class  = $this->_get_full_name($name);
        $object = $this->objects[$class] ?? $this->createObject($class, [], false);
        
        $this->set($name, $object);
    }
    
    /**
     * 获取类全称
     *
     * @param string $name
     *
     * @return string
     */
    private function _get_full_name(string $name): string
    {
        if (!isset($this->definitions[$name])) return ucfirst($name);

        $r = $this->definitions[$name];

        if (is_string($r)) return $r;
        if(is_array($r) && isset($r['full']) && is_string($r['full'])) return $r['full'];

        return ucfirst($name);
    }

    /**
     * 是否服务类(单一类)
     *
     * @param string $name
     * @return bool
     * @noinspection PhpUnusedPrivateMethodInspection
     */
    private function _is_single(string $name): bool
    {
        if (!isset($this->definitions[$name])) return true;

        $r = $this->definitions[$name];
        if (is_string($r)) return true;

        return !is_array($r) || !isset($r['single']) || $r['single'];
    }
    
    /**
     * ## 创建对象
     *
     * @param string $class
     * @param array  $arguments
     * @param bool   $short true: 获取系统定义
     *
     * @return object
     * @throws \Exception
     */
    public function createObject(string $class, array $arguments = [], bool $short = true): object
    {
        if ($short === true) $class = $this->_get_full_name($class);
        
        if (!class_exists($class)) throw new Exception(self::ERR_CLS_NOT_FOUND, ['class' => $class]);
        
        try {
            $kernel     = $this->Kernel();
            $reflection = new ReflectionClass($class);
            $obj        = $this->_make_instance($reflection, $arguments);
            
            // 处理类注解
            foreach ($reflection->getAttributes() as $attribute) {
                /** @var ClassInterface $instance */
                $instance = $attribute->newInstance();
                $instance->forClass($kernel, $obj);
            }
            
            return $obj;
            
        } catch (Throwable $e) {
            throw new Exception(
                self::ERR_OBJ_CREATE_FAILED,
                ['obj' => $class, 'err' => $e->getMessage()]
            );
        }
    }
    
    /**
     * 处理 参数注解
     *
     * @param ReflectionClass $reflection
     * @param array           $arguments
     *
     * @return object
     * @throws \Exception
     */
    private function _make_instance(ReflectionClass $reflection, array $arguments = []): object
    {
        $kernel      = $this->Kernel();
        $constructor = $reflection->getConstructor();
        
        // 没有参数
        if (!$constructor or count($constructor->getParameters()) === 0)
            return $reflection->newInstance();
        
        $args = [];
        
        // 处理参数注解
        foreach ($constructor->getParameters() as $parameter) {
            $attributes = $parameter->getAttributes();
            if (count($attributes) > 0) {
                foreach ($attributes as $attribute) {
                    /** @var ParameterInterface $instance */
                    $instance = $attribute->newInstance();
                    // 参数填充完成， 直接处理下一个参数
                    if ($instance->forParameters($kernel, $parameter, $args)) continue 2;
                }
            }
            if (!empty($arguments)) $args[] = array_shift($arguments);
        }
        
        return $reflection->newInstanceArgs($args);
    }
    
    /**
     * ## 预置服务
     *
     * @param string $name
     * @param object $object
     */
    public function set(string $name, object $object): void
    {
        $this->objects[$name]          = $object;
        $this->objects[$object::class] = $object;
    }
    
    /**
     * ## 是否存在原始定义
     *
     * * 返回`true`不能保证对象创建成功
     *
     * @param string $name
     *
     * @return bool
     */
    public function has(string $name): bool
    {
        return isset($this->definitions[$name]);
    }
    
    /**
     * @param string $name
     * @param array  $arguments 参数为空，则优先返回服务
     *
     * @return object
     * @throws \Exception
     */
    public function __call(string $name, array $arguments = []): object
    {
        if (!empty($arguments)) return $this->createObject($name, $arguments);
        return $this->get($name);
    }
}